package com.gframework.boot.mvc.controller.convert;

import java.io.IOException;
import java.text.DateFormat;
import java.text.FieldPosition;
import java.text.ParsePosition;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;

import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.lang.Nullable;

import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;

/**
 * 本类将作为springmvc json序列化的首选ObjectMapper类对象.
 * <p>本类对controller中存在的诸多问题都进行了统一处理。
 * <ul>
 * <li>1、long精度丢失问题，js的整形最多16~17位，超出则会发生精度丢失，所以此ObjectMapper会判断如果long类型的值大于这个节点，则会转换为string返回</li>
 * <li>对Date、LocalDate、LocalDateTime三个日期类型的统一前后端通讯格式进行统一转换。这个是通过{@link UnifiedDateConverter}接口实现的。
 * </ul>
 * 默认使用的是一个以时间戳为标准的转换器，你可以通过实现自己的bean来覆盖默认的bean以实现自定义的统一日期格式</li>
 * 
 * @since 1.0.0
 * @author Ghwolf
 * @see UnifiedDateConverter
 */
@SuppressWarnings("serial")
public class CompatibleObjectMapper extends ObjectMapper implements InitializingBean {
	/**
	 * js中不丢失精度的整形较大值（非最大值，但是这个值基本处于9_000_000_000_000_000 到 10_000_000_000_000_000之间）
	 */
	private static final long MAX_JS_LONG_NUMBER = 9_000_000_000_000_000L ;
	/**
	 * 日期转换器
	 */
	@Nullable
	@Lazy
	@Autowired(required=false)
	private UnifiedDateConverter dateConvert ;
	
	public CompatibleObjectMapper(){
		LoggerFactory.getLogger(CompatibleObjectMapper.class).info(
				"====> 已启用 CompatibleObjectMapper 扩展MVC ObjectMapper");
	}
	
	/**
	 * 字符串转日期转换器(Date)
	 * <p>本类禁止clone以提高性能，并扩展到通过{@link UnifiedDateConverter}进行具体的日期转换操作
	 * @since 1.0.0
	 * @author Ghwolf
	 *
	 */
	class TimestampDateFormat extends DateFormat {

		@Override
		public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) {
			Object o = CompatibleObjectMapper.this.dateConvert.dateToStringOrNumber(date);
			if (o instanceof Number) {
				return toAppendTo.append(((Number) o).longValue());
			} else {
				return toAppendTo.append(o);
			}
		}

		/**
		 * jackson的ObjectMapper处理类会在每次格式化的时候克隆一个本类对象，本类只是一个针对时间戳的格式化处理，无任何复杂操作，因此直接返回当前对象即可。
		 */
		@Override
		public Object clone() {
			return this;
		}

		@Override
		public Date parse(String source, ParsePosition pos) {
			pos.setIndex(source.length() - 1);
			return CompatibleObjectMapper.this.dateConvert.stringToDate(source);
		}

	}

	@Override
	public void afterPropertiesSet() throws Exception {
		// 忽略不存在的列
		this.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
		// 不返回空值
		this.setSerializationInclusion(Include.NON_NULL);

		SimpleModule module = new SimpleModule();
		// long 过大转换为字符串返回防止进度丢失
		module.addSerializer(Long.class,new ToStringSerializer(){
			@Override
			public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
				if (value instanceof Long) {
					Long v  = (Long) value ;
					if (v > MAX_JS_LONG_NUMBER) {
						gen.writeString(value.toString());
					} else {
						gen.writeNumber(v);
					}
				} else {
					super.serialize(value, gen, provider);
				}
			}
		});
		
		if (this.dateConvert != null) {
			// 日期转换时间戳
			this.setDateFormat(new TimestampDateFormat());
			// LocalDate 序列化
			module.addSerializer(LocalDate.class,new ToStringSerializer(){
				@Override
				public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
					if (value instanceof LocalDate) {
						LocalDate v  = (LocalDate) value ;
						Object o = CompatibleObjectMapper.this.dateConvert.localDateToStringOrNumber(v);
						if (o instanceof Number) {
							gen.writeNumber(((Number) o).longValue());
						} else {
							gen.writeString(String.valueOf(o));
						}
					} else {
						super.serialize(value, gen, provider);
					}
				}
			});
			// LocalDateTime 序列化
			module.addSerializer(LocalDateTime.class,new ToStringSerializer(){
				@Override
				public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
					if (value instanceof LocalDateTime) {
						LocalDateTime v  = (LocalDateTime) value ;
						Object o = CompatibleObjectMapper.this.dateConvert.localDateTimeToStringOrNumber(v);
						if (o instanceof Number) {
							gen.writeNumber(((Number) o).longValue());
						} else {
							gen.writeString(String.valueOf(o));
						}
					} else {
						super.serialize(value, gen, provider);
					}
				}
			});
		}
		this.registerModule(module);
	}

}
